package tcln.sors.model.core.dao;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;

import tcln.commons.assertion.Assert;
import tcln.commons.lang.StringUtils;
import tcln.sors.model.core.criteria.SearchCriteria;
import tcln.sors.model.core.criteria.SearchCriterion;
import tcln.sors.model.core.criteria.SortInfo;
import tcln.sors.model.core.criteria.SortOrder;

/**
 * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
 */
public class DetachedCriteriaBuilder {
    private DetachedCriteria destinationCriteria;

    /**
     * Returns a new instance of {@link DetachedCriteriaBuilder} for {@link DetachedCriteria} construction.
     * 
     * @param destCriteria
     *            Destination criteria which is filled by other kinds of criteria.
     * @return
     */
    public static DetachedCriteriaBuilder create(DetachedCriteria destCriteria) {
        return new DetachedCriteriaBuilder(destCriteria);
    }

    private DetachedCriteriaBuilder(DetachedCriteria destCriteria) {
        Assert.ARGUMENTS.notNull(destCriteria, "Destination DetachedCriteria is missing");
        this.destinationCriteria = destCriteria;
    }

    /**
     * Builds a new {@link DetachedCriteria} from a {@link SearchCriteria}.
     * 
     * @param destCriteria
     * @param clientCriteria
     * @return
     */
    public DetachedCriteriaBuilder addCriteria(SearchCriteria clientCriteria) {
        Assert.ARGUMENTS.notNull(clientCriteria, "Source SearchCriteria is missing");

        Map<String, DetachedCriteria> subCriteriaMap = new HashMap<String, DetachedCriteria>();

        List<SearchCriterion> criterions = clientCriteria.getCriterions();
        for (SearchCriterion criterion : criterions) {
            String[] propertyParts = StringUtils.split(criterion.getProperty(), ".");

            // add all sub-criteria
            DetachedCriteria subCriteria = destinationCriteria;
            if (propertyParts.length >= 2) {
                for (int i = 0; i < propertyParts.length - 1; i++) {
                    String subCriteriaAssocPath = StringUtils.join(propertyParts, ".", 0, i);
                    DetachedCriteria subCri = subCriteriaMap.get(subCriteriaAssocPath);
                    if (subCri != null) {
                        subCriteria = subCri;
                    } else {
                        subCriteria = subCriteria.createCriteria(propertyParts[i]);
                        subCriteriaMap.put(subCriteriaAssocPath, subCriteria);
                    }
                }
            }

            // resolve last property
            String directProperty = propertyParts[propertyParts.length - 1];

            // build criterion
            Criterion destCriterion;
            switch (criterion.getOperator()) {
                case EQUAL:
                    destCriterion = Restrictions.eq(directProperty, criterion.getValues()[0]);
                    break;
                case NOT_EQUAL:
                    destCriterion = Restrictions.ne(directProperty, criterion.getValues()[0]);
                    break;
                case GREATER_THAN:
                    destCriterion = Restrictions.gt(directProperty, criterion.getValues()[0]);
                    break;
                case NOT_GREATER_THAN:
                    destCriterion = Restrictions.le(directProperty, criterion.getValues()[0]);
                    break;
                case LESS_THAN:
                    destCriterion = Restrictions.lt(directProperty, criterion.getValues()[0]);
                    break;
                case NOT_LESS_THAN:
                    destCriterion = Restrictions.ge(directProperty, criterion.getValues()[0]);
                    break;
                case LIKE:
                    destCriterion = Restrictions.like(directProperty, criterion.getValues()[0]);
                    break;
                case NOT_NULL:
                    destCriterion = Restrictions.isNotNull(directProperty);
                    break;
                case NULL:
                    destCriterion = Restrictions.isNull(directProperty);
                    break;
                case BETWEEN:
                    destCriterion = Restrictions.between(criterion.getProperty(), criterion.getValues()[0], criterion
                            .getValues()[1]);
                    break;
                case IN:
                    destCriterion = Restrictions.in(criterion.getProperty(), criterion.getValues());
                    break;
                default:
                    throw new UnsupportedOperationException("Unsupported criterion operator " + criterion.getOperator());
            }

            // build full criteria
            subCriteria.add(destCriterion);
        }

        return this;
    }

    /**
     * @param sortInfo
     * @return
     */
    public DetachedCriteriaBuilder addOrderCriteria(SortInfo sortInfo) {
        if (sortInfo != null) {
            for (Entry<String, SortOrder> entry : sortInfo.getSortingInfo().entrySet()) {
                if (entry.getValue() == SortOrder.ASC) {
                    destinationCriteria.addOrder(Order.asc(entry.getKey()));
                } else if (entry.getValue() == SortOrder.DESC) {
                    destinationCriteria.addOrder(Order.desc(entry.getKey()));
                } else {
                    // nothing to do
                }
            }
        }
        return this;
    }
}
